package gm.server;

import gm.GmLog;
import gm.server.geom.Vector2;
import java.awt.geom.Point2D;
import java.util.ArrayList;

/**
 * Munkaterület.
 *
 * @author imruf84
 */
public class WorkingArea {

    /**
     * Végpont.
     */
    private final Tip tip;
    /**
     * Első sarok.
     */
    private final Point2D corner1Local = new Point2D.Double();
    private final Point2D corner1Global = new Point2D.Double();
    /**
     * Második sarok.
     */
    private final Point2D corner2Local = new Point2D.Double();
    private final Point2D corner2Global = new Point2D.Double();
    /**
     * Első sarok koordinátái abszolút motor lépésekben.
     */
    private final double corner1Steps[] = {0, 0};
    /**
     * Második sarok koordinátái abszolút motor lépésekben.
     */
    private final double corner2Steps[] = {0, 0};
    /**
     * Sarkok megváltozásának az eseménye.
     */
    private final ArrayList<WorkingAreaCornerChangeEvent> onChangeCornerEvents = new ArrayList<>();

    /**
     * Konstruktor.
     *
     * @param tip végpont
     */
    public WorkingArea(final Tip tip) {
        this.tip = tip;
    }

    /**
     * Sarokpontok lekérdezése rendezve.
     *
     * @param local igaz esetén lokális koordinátákkal dolgozunk
     * @return sarokpontok rendezve
     */
    public Point2D[] getBoundCorners(final boolean local) {

        Point2D min = new Point2D.Double(
                Math.min(getCorner1(local).getX(), getCorner2(local).getX()),
                Math.min(getCorner1(local).getY(), getCorner2(local).getY())
        );
        Point2D max = new Point2D.Double(
                Math.max(getCorner1(local).getX(), getCorner2(local).getX()),
                Math.max(getCorner1(local).getY(), getCorner2(local).getY())
        );

        return new Point2D[]{min, max};
    }

    /**
     * Bal felső sarok lekérdezése motor lépésekben.
     *
     * @param m motor
     * @return lépések
     * @throws WrongPositionException kivétel
     */
    public double[] getTopLeftCornerInStepsAbsolute(final Machine m) throws WrongPositionException {
        Point2D tl = getBoundCorners(true)[0];
        Machine mc = m.getClone();

        if (getCorner1(true).getX() > getCorner2(true).getX()) {
            mc.getMotorLeft().setCurrentStepsSilent(getCorner1StepsAbsolute()[0]);
            mc.getMotorRight().setCurrentStepsSilent(getCorner1StepsAbsolute()[1]);
        } else {
            mc.getMotorLeft().setCurrentStepsSilent(getCorner2StepsAbsolute()[0]);
            mc.getMotorRight().setCurrentStepsSilent(getCorner2StepsAbsolute()[1]);
        }

        if (mc.getTip().calculatePositionLocal().y > tl.getY()) {
            while (mc.getTip().calculatePositionLocal().y > tl.getY()) {
                mc.getMotorLeft().setCurrentStepsSilent(mc.getMotorLeft().getCurrentStepsAbsolute() - 1);
                mc.getMotorRight().setCurrentStepsSilent(mc.getMotorRight().getCurrentStepsAbsolute() - 1);
            }
        } else {
            while (mc.getTip().calculatePositionLocal().y < tl.getY()) {
                mc.getMotorLeft().setCurrentStepsSilent(mc.getMotorLeft().getCurrentStepsAbsolute() + 1);
                mc.getMotorRight().setCurrentStepsSilent(mc.getMotorRight().getCurrentStepsAbsolute() + 1);
            }
        }

        return new double[]{mc.getMotorLeft().getCurrentStepsAbsolute(), mc.getMotorRight().getCurrentStepsAbsolute()};
    }

    /**
     * Pont munkaterületen belül való tesztelése.
     *
     * @param p pont
     * @param local igaz esetén lokális koordinátákkal dolgozunk
     * @return igaz ha belül van egyébként hamis
     */
    public boolean containsPoint(Point2D p, final boolean local) {

        Point2D c[] = getBoundCorners(local);
        Point2D min = c[0];
        Point2D max = c[1];

        return ((p.getX() <= max.getX()) && (p.getY() <= max.getY()) && (p.getX() >= min.getX()) && (p.getY() >= min.getY()));
    }

    /**
     * Végpont munkaterületen belül való tesztelése.
     *
     * @return igaz ha belül van egyébként hamis
     * @throws gm.server.WrongPositionException kivétel
     */
    public boolean containsTip() throws WrongPositionException {
        Vector2 v = getTip().calculatePositionLocal();
        return containsPoint(new Point2D.Double(v.x, v.y), true);
    }

    /**
     * Első sarok koordinátáinak tárolása.
     *
     * @param steps1 első koordináta
     * @param steps2 második koordináta
     */
    public void storeCorner1(double steps1, double steps2) {
        try {
            corner1Steps[0] = steps1;
            corner1Steps[1] = steps2;

            // A számoláshoz átállítjuk a motorokat.
            double oldSteps1 = getMotor1().getCurrentStepsAbsolute();
            double oldSteps2 = getMotor2().getCurrentStepsAbsolute();
            getMotor1().setCurrentSteps(steps1, false);
            getMotor2().setCurrentSteps(steps2, false);

            // Lokális koordináták frissítése.
            Vector2 tl = getTip().calculatePositionLocal();
            corner1Local.setLocation(tl.x, tl.y);

            // Globális koordináták frissítése.
            Vector2 tg = getTip().calculatePositionGlobal();
            corner1Global.setLocation(tg.x, tg.y);

            // Motorok eredeti állapotának a visszaállítása.
            getMotor1().setCurrentSteps(oldSteps1, false);
            getMotor2().setCurrentSteps(oldSteps2, false);

            fireOnCornerChangeEvents(1);
        } catch (WrongPositionException ex) {
            GmLog.printError(ex);
        }
    }

    /**
     * Első sarok tárolása (abszolút motorkoordináták).
     */
    public void storeCorner1ToCurrent() {
        storeCorner1(getMotor1().getCurrentStepsAbsolute(), getMotor2().getCurrentStepsAbsolute());
    }

    /**
     * Második sarok koordinátáinak tárolása.
     *
     * @param steps1 első koordináta
     * @param steps2 második koordináta
     */
    public void storeCorner2(double steps1, double steps2) {
        try {
            corner2Steps[0] = steps1;
            corner2Steps[1] = steps2;

            // A számoláshoz átállítjuk a motorokat.
            double oldSteps1 = getMotor1().getCurrentStepsAbsolute();
            double oldSteps2 = getMotor2().getCurrentStepsAbsolute();
            getMotor1().setCurrentSteps(steps1, false);
            getMotor2().setCurrentSteps(steps2, false);

            // Lokális koordináták frissítése.
            Vector2 tl = getTip().calculatePositionLocal();
            corner2Local.setLocation(tl.x, tl.y);

            // Globális koordináták frissítése.
            Vector2 tg = getTip().calculatePositionGlobal();
            corner2Global.setLocation(tg.x, tg.y);

            // Motorok eredeti állapotának a visszaállítása.
            getMotor1().setCurrentSteps(oldSteps1, false);
            getMotor2().setCurrentSteps(oldSteps2, false);

            fireOnCornerChangeEvents(2);
        } catch (WrongPositionException ex) {
            GmLog.printError(ex);
        }
    }

    /**
     * Második sarok tárolása (abszolút motorkoordináták).
     */
    public void storeCorner2ToCurrent() {
        storeCorner2(getMotor1().getCurrentStepsAbsolute(), getMotor2().getCurrentStepsAbsolute());
    }

    /**
     * Első sarok lekérdezése.
     *
     * @param local igaz esetén a locális koordináták lesznek lekérdezve
     * @return első sarok
     */
    public Point2D getCorner1(final boolean local) {
        return local ? corner1Local : corner1Global;
    }

    /**
     * Második sarok lekérdezése.
     *
     * @param local igaz esetén a locális koordináták lesznek lekérdezve
     * @return második sarok
     */
    public Point2D getCorner2(final boolean local) {
        return local ? corner2Local : corner2Global;
    }

    /**
     * Végpont lekérdezése.
     *
     * @return végpont
     */
    public Tip getTip() {
        return tip;
    }

    /**
     * Első motor lekérdezése.
     *
     * @return első motor
     */
    public Motor getMotor1() {
        return getTip().getHead().getArm1().getNut().getRod().getMotor();
    }

    /**
     * Második motor lekérdezése.
     *
     * @return második motor
     */
    public Motor getMotor2() {
        return getTip().getHead().getArm2().getNut().getRod().getMotor();
    }

    /**
     * Sarok megváltozásának az eseményének a hozzáadása.
     *
     * @param event esemény
     */
    public void addOnCornerChangeEvent(final WorkingAreaCornerChangeEvent event) {
        onChangeCornerEvents.add(event);
    }

    /**
     * Sarok megváltozásának az eseményének a futtatása.
     *
     * @param cornerIndex sarok indexe
     */
    private void fireOnCornerChangeEvents(final int cornerIndex) {
        onChangeCornerEvents.stream().forEach((event) -> {
            event.onCornerChange(this, cornerIndex);
        });
    }

    /**
     * Első sarok koordinátáinak lekérdezése abszolút motor pozícióban.
     *
     * @return első sarok koordinátái
     */
    public double[] getCorner1StepsAbsolute() {
        return corner1Steps;
    }

    /**
     * Második sarok koordinátáinak lekérdezése abszolút motor pozícióban.
     *
     * @return második sarok koordinátái
     */
    public double[] getCorner2StepsAbsolute() {
        return corner2Steps;
    }

}
